/* *****************************************************************************
 * JFab (http://code.google.com/p/jfab)
 * Copyright (c) 2011 JFab.org
 * Admin jfab@jeffreythompson.net
 *
 * See the file "LICENSE.txt" for information on usage and redistribution of
 * this file, and for a DISCLAIMER OF ALL WARRANTIES.
 * *****************************************************************************/

package org.jfab.core.model;

import java.util.Map;

import org.apache.commons.lang3.ObjectUtils.Null;
import org.apache.commons.lang3.StringUtils;
import org.javaruntype.exceptions.TypeRecognitionException;
import org.javaruntype.type.Type;
import org.javaruntype.type.Types;
import org.javaruntype.typedef.TypeDef;
import org.javaruntype.typedef.TypeDefs;

/**
 * Provides a type factory.
 */
public final class DefaultTypeFactory implements TypeFactory
{
    @Override
    public final Type<?> createType(final Class<?> aClass)
    {
        return Types.forClass(aClass);
    }

    @Override
    public final Type<?> createType(final Map<String, Type<?>> importMap,
            final String className)
    {
        final String packageName = null;

        return createType(packageName, importMap, className);
    }

    @Override
    public final Type<?> createType(final String codeFragment)
    {
        Type<?> answer = null;

        if (StringUtils.isNotEmpty(codeFragment)
                && !"void".equals(codeFragment))
        {
            answer = Types.forName(codeFragment);
        }

        return answer;
    }

    @Override
    public final Type<?> createType(final String packageName,
            final Map<String, Type<?>> importMap, final String className)
    {
        Type<?> answer = null;

        if (StringUtils.isEmpty(packageName) && (importMap == null))
        {
            answer = createType(className);
        }
        else if (StringUtils.isNotEmpty(className))
        {
            String myCodeFragment = className;
            final int index0 = myCodeFragment.indexOf('<');

            if (index0 >= 0)
            {
                final int index1 = myCodeFragment.indexOf('>');
                String text0 = StringUtils.substringBetween(myCodeFragment,
                        "<", ">");
                text0 = text0.trim();

                final String[] parts = text0.split(",");

                final StringBuilder sb = new StringBuilder();
                final int size = parts.length;

                for (int i = 0; i < size; i++)
                {
                    final Type<?> type = createTypeHelper(packageName,
                            importMap, parts[i]);

                    if (type != null)
                    {
                        final String part = type.getName();
                        sb.append(part);
                    }
                    else
                    {
                        sb.append("?");
                    }

                    if (i < (size - 1))
                    {
                        sb.append(", ");
                    }
                }

                myCodeFragment = myCodeFragment.substring(0, index0 + 1)
                        + sb.toString() + myCodeFragment.substring(index1);
                answer = Types.forName(myCodeFragment);
            }
            else
            {
                answer = createTypeHelper(packageName, importMap, className);
            }
        }

        return answer;
    }

    @Override
    public final TypeDef createTypeDef(final Class<?> aClass)
    {
        TypeDef answer = null;

        if ((aClass != null) && (aClass != Null.class))
        {
            answer = TypeDefs.forClass(aClass);
        }

        return answer;
    }

    @Override
    public final TypeDef createTypeDef(final String codeFragment)
    {
        TypeDef answer = null;

        if (StringUtils.isNotEmpty(codeFragment)
                && !"void".equals(codeFragment))
        {
            final Type<?> type = createTypeForTypeDef(codeFragment);

            if (type != null)
            {
                answer = TypeDefs.forClass(type.getRawClass());
            }
        }

        return answer;
    }

    /**
     * @param codeFragment Code fragment.
     * 
     * @return a new type.
     */
    private Type<?> createTypeForTypeDef(final String codeFragment)
    {
        Type<?> answer = null;

        if (StringUtils.isNotEmpty(codeFragment))
        {
            String myCodeFragment = codeFragment.trim();
            final int index = myCodeFragment.indexOf('<');

            if (index >= 0)
            {
                myCodeFragment = myCodeFragment.substring(0, index);
            }

            answer = createType(myCodeFragment);
        }

        return answer;
    }

    /**
     * @param packageName Package name.
     * @param importMap Map of simple type name to type.
     * @param className Class name.
     * 
     * @return a new type.
     */
    private Type<?> createTypeHelper(final String packageName,
            final Map<String, Type<?>> importMap, final String className)
    {
        Type<?> answer = null;

        String myClassName = className.trim();

        if (!myClassName.contains("."))
        {
            Type<?> type = null;

            if (importMap != null)
            {
                type = importMap.get(myClassName);
            }

            if (type != null)
            {
                myClassName = type.getName();
            }
            else if (StringUtils.isNotEmpty(packageName))
            {
                myClassName = packageName + "." + myClassName;
            }
        }

        try
        {
            answer = Types.forName(myClassName);
        }
        catch (final TypeRecognitionException e)
        {
            try
            {
                answer = Types.forName(className);
            }
            catch (final TypeRecognitionException e2)
            {
                System.err.println(e2.getMessage());
            }
        }

        return answer;
    }
}
